package com.lulin.mall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.lulin.mall.product.service.CategoryBrandRelationService;
import com.lulin.mall.product.vo.Catalog2VO;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lulin.common.utils.PageUtils;
import com.lulin.common.utils.Query;

import com.lulin.mall.product.dao.CategoryDao;
import com.lulin.mall.product.entity.CategoryEntity;
import com.lulin.mall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import sun.rmi.runtime.Log;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedissonClient redissonClient;

    /**
     * 更新品牌和类目的信息
     *
     * @CacheEvict：在更新类目的时候，删掉缓存。这个是一级，下面的小类也要删除，
     *              因此，是一个组合 用另外一注解@Caching
     */
    //@CacheEvict(value = "catagory", key = "'getLeve1Category'")
    @Caching(evict = {
            @CacheEvict(value = "catagory", key = "'getLeve1Category'"),
            @CacheEvict(value = "catagory", key = "'getCatelog2JSON'")
    })
    @Transactional      //事务管理
    @Override
    public void updateDetail(CategoryEntity category) {
        //更新类目id，更新类目表的数据
        this.updateById(category);

        //获取类目名称，如果不为空，就根据类别id，更新关联表中的类目名称
        if (!StringUtils.isEmpty(category.getName())) {
            //根据类别id，更新关联表中的类目名称
            categoryBrandRelationService.updateCatelogName(category.getCatId(), category.getName());
        }
    }

    /**
     * 查询出所有的一级分类
     *
     * @Cacheable({"catagory","product"}) 代表当前的方法返回的结果需要缓存，
     * 如果缓存中有数据，该方法不会执行。没有数据，就执行该方法，并且把查询的结果缓存起来。
     * 在使用中，可以指定缓存的名称， 起到分区的作用，一般按照业务来区分
     */
    //@Cacheable(value = {"catagory", "product"}, key = "#root.methodName")
    @Cacheable(value = {"catagory"}, key = "#root.methodName")
    @Override
    public List<CategoryEntity> getLeve1Category() {
        //long start = System.currentTimeMillis();
        //查询出所有的一级分类
        List<CategoryEntity> list = baseMapper.queryLeve1Category();
        // long end = System.currentTimeMillis();
        //System.out.println("查询所消费的时间:" + (end - start));
        return list;
    }

    /**
     * 根据父编号，获取对应的子菜单信息
     *
     * @param parentCid 父编号
     * @param list
     * @return
     */
    private List<CategoryEntity> queryByParenCid(List<CategoryEntity> list, Long parentCid) {
        List<CategoryEntity> collect = list.stream().filter(item -> {
            return item.getParentCid().equals(parentCid);
        }).collect(Collectors.toList());
        return collect;
    }

    /**
     * springCache缓存：
     * 查询出所有分类数据，并且完成一级、二级和三级分类的关联
     */
    @Cacheable(value = "catagory", key = "#root.methodName")
    @Override
    public Map<String, List<Catalog2VO>> getCatelog2JSON() {
        //获取所有分类的数据
        List<CategoryEntity> list = baseMapper.selectList(new QueryWrapper<>());
        // 获取所有的一级分类的数据
        List<CategoryEntity> leve1Category = this.queryByParenCid(list, 0L);
        //把一级分类的数据转为map，key是一级分类的id，value值是一级分类对应的二级分类的数据
        Map<String, List<Catalog2VO>> map = leve1Category.stream().collect(
                Collectors.toMap(
                        key -> key.getCatId().toString(),
                        value -> {
                            //根据一级类目的编号(分类id)，查询对应索引的二级类目
                            List<CategoryEntity> l2Catalogs = this.queryByParenCid(list, value.getCatId());
                            //把查询出来二级分类的数据，填充到Catalog2VO中
                            List<Catalog2VO> catalog2VOS2 = null;
                            if (l2Catalogs != null) {
                                catalog2VOS2 = l2Catalogs.stream().map(l2 -> {
                                    //把查询出来二级分类的数据，填充到Catalog2VO中
                                    Catalog2VO catalog2VO = new Catalog2VO(
                                            l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName());

                                    //根据二级类目的编号，查询对应索引的三级类目
                                    List<CategoryEntity> l3Catelogs = queryByParenCid(list, l2.getCatId());
                                    if (l3Catelogs != null) {
                                        // 获取到的二级分类对应的三级分类的数据
                                        List<Catalog2VO.Catalog3VO> catalog3VOList = l3Catelogs.stream().map(l3 -> {
                                            Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(
                                                    l3.getParentCid().toString(), l3.getCatId().toString(), l3.getName()
                                            );
                                            return catalog3VO;
                                        }).collect(Collectors.toList());
                                        //三级分类关联二级分类
                                        catalog2VO.setCatalog3List(catalog3VOList);
                                    }
                                    return catalog2VO;
                                }).collect(Collectors.toList());
                            }
                            return catalog2VOS2;
                        }
                ));
        return map;
    }

    /**
     * redis缓存：
     * 查询出所有的二级和三级分类的数据
     */
    public Map<String, List<Catalog2VO>> getCatelog2JSONRedis() {
        String key = "catelogJSON";
        // 从Redis中，获取分类的信息
        String catelogJSON = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isEmpty(catelogJSON)) {
            System.out.println("--> 缓存没有命中");
            //缓存中没数据，就从数据库中查询
            Map<String, List<Catalog2VO>> catelog2JSONForBb = getCatelog2JSONForRedisson();
            // Map<String, List<Catalog2VO>> catelog2JSONForBb = getCatelog2JSONForBb();
            if (catelog2JSONForBb == null) {
                //说明数据库中也不存在   5秒过期   ——防止缓存穿透
                stringRedisTemplate.opsForValue().set(key, "1", 5, TimeUnit.SECONDS);
            } else {
                //从数据库中查询到数据，缓存到redis中
                //对象转为json
                String json = JSON.toJSONString(catelog2JSONForBb);
                //保存到redis，并加随机值（解决缓存雪崩）——小时
                stringRedisTemplate.opsForValue().set(key, json, new Random().nextInt(10), TimeUnit.HOURS);
            }
            return catelog2JSONForBb;
        }
        System.out.println("--> 缓存命中了。。。");
        //表示命中了数据，就从redis缓存中获取信息返回
        Map<String, List<Catalog2VO>> stringListMap =
                JSON.parseObject(catelogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
                });
        return stringListMap;
    }


    /**
     * Redisson 实现分布式锁——从数据库中查询
     */
    public Map<String, List<Catalog2VO>> getCatelog2JSONForRedisson() {
        String keys = "catelogJSON";
        // 获取分布式锁对象——加锁时，锁的名称一定要注意锁的粒度，例如：商品的锁，product-1001-lock，product-1002-lock
        RLock lock = redissonClient.getLock("catelog2JSON-lock");

        Map<String, List<Catalog2VO>> dataForDB = null;
        try {
            //加锁
            lock.lock();
            //从数据库中查询数据操作
            dataForDB = getDataForDB(keys);
        } finally {
            //释放锁
            lock.unlock();
        }
        return dataForDB;
    }

    /**
     * redis实现分布式锁———从数据库中查询
     */
    public Map<String, List<Catalog2VO>> getCatelog2JSONForRedisLock() {
        String keys = "catelogJSON";
        // 加锁
        //插入时，搞1个uuid来区分，后面释放锁时，根据uuid来判断
        String uuid = UUID.randomUUID().toString();
        //在执行插入操作的同时,设置了过期时间
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
        if (lock) {
            //加锁成功
            Map<String, List<Catalog2VO>> dataForDB = null;
            try {
                //从数据库中查询数据操作
                dataForDB = getDataForDB(keys);
            } finally {
                //定义1个lua脚本———通过Redis的lua脚本，实现 询和删除操作的原子性（官方的）
                String srcipts = "if redis.call('get',KEYS[1]) == ARGV[1]  then return redis.call('del',KEYS[1]) else  return 0 end ";
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(srcipts, Long.class), Arrays.asList("lock"), uuid);
            }
            return dataForDB;
        } else {
            //加锁失败
            //休眠+重试
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("获取锁失败。。。");
            return getCatelog2JSONForRedisLock();

        }
    }

    /**
     * 从数据库中查询数据操作
     */
    private Map<String, List<Catalog2VO>> getDataForDB(String keys) {
        //先到redis缓存中查询是否有数据，如果有，就返回，没有就去查数据库
        String catelogJSON = stringRedisTemplate.opsForValue().get(keys);
        if (!StringUtils.isEmpty(catelogJSON)) {
            //说明缓存命中了
            //表示命中了数据，就从redis缓存中获取信息返回
            Map<String, List<Catalog2VO>> stringListMap =
                    JSON.parseObject(catelogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
                    });
            return stringListMap;
        }
        System.out.println("-------> 查询数据库操作.....");

        //获取所有分类的数据
        List<CategoryEntity> list = baseMapper.selectList(new QueryWrapper<>());
        // 获取所有的一级分类的数据
        List<CategoryEntity> leve1Category = this.queryByParenCid(list, 0L);
        //把一级分类的数据转为map，key是一级分类的id，value值是一级分类对应的二级分类的数据
        Map<String, List<Catalog2VO>> map = leve1Category.stream().collect(
                Collectors.toMap(
                        key -> key.getCatId().toString(),
                        value -> {
                            //根据一级类目的编号(分类id)，查询对应索引的二级类目
                            List<CategoryEntity> l2Catalogs = this.queryByParenCid(list, value.getCatId());
                            //把查询出来二级分类的数据，填充到Catalog2VO中
                            List<Catalog2VO> catalog2VOS2 = null;
                            if (l2Catalogs != null) {
                                catalog2VOS2 = l2Catalogs.stream().map(l2 -> {
                                    //把查询出来二级分类的数据，填充到Catalog2VO中
                                    Catalog2VO catalog2VO = new Catalog2VO(
                                            l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName());

                                    //根据二级类目的编号，查询对应索引的三级类目
                                    List<CategoryEntity> l3Catelogs = queryByParenCid(list, l2.getCatId());
                                    if (l3Catelogs != null) {
                                        // 获取到的二级分类对应的三级分类的数据
                                        List<Catalog2VO.Catalog3VO> catalog3VOList = l3Catelogs.stream().map(l3 -> {
                                            Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(
                                                    l3.getParentCid().toString(), l3.getCatId().toString(), l3.getName()
                                            );
                                            return catalog3VO;
                                        }).collect(Collectors.toList());
                                        //三级分类关联二级分类
                                        catalog2VO.setCatalog3List(catalog3VOList);
                                    }
                                    return catalog2VO;
                                }).collect(Collectors.toList());
                            }
                            return catalog2VOS2;
                        }
                ));
        //从数据库中获取到的数据，保存到缓存中
        //cache.put("getCatelog2JSON", map);
        if (map == null) {
            //说明数据库中也不存在   5秒过期   ——防止缓存穿透
            stringRedisTemplate.opsForValue().set(keys, "1", 5, TimeUnit.SECONDS);
        } else {
            //从数据库中查询到数据，缓存到redis中
            //对象转为json
            String json = JSON.toJSONString(map);
            //保存到redis，并加随机值（解决缓存雪崩）——小时
            stringRedisTemplate.opsForValue().set(keys, json, new Random().nextInt(10), TimeUnit.HOURS);
        }
        return map;
    }


    /**
     * 从数据库查询的结果：
     * 查询出所有的二级和三级分类的数据,并封装为Map<String, Catalog2VO>对象
     * ——Map<一级分类id, 二级分类值>
     */
    public Map<String, List<Catalog2VO>> getCatelog2JSONForBb() {
        String keys = "catelogJSON";
        //先到redis缓存中查询是否有数据，如果有，就返回，没有就去查数据库
        return getDataForDB(keys);
    }


    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 查询所有的类别数据，将数据封装为树形结构，便于前端使用
     */
    @Override
    public List<CategoryEntity> queryPageWithTree(Map<String, Object> params) {
        //1、查询所有的商品分类信息
        List<CategoryEntity> categoryEntityList = baseMapper.selectList(null);

        //2、 将商品分类信息，拆解为树形结构【父子关系】
        //第一步：遍历出所有的大类 parent_cid = 0   （递归方式——jdk8特性-stream流）
        //第二步：根据大类，找到对应的小类
        List<CategoryEntity> list = categoryEntityList.stream()
                //过滤出所有的大类(parent_cid = 0  )，true：满足过滤条件，就留下，false：不满足。就过滤掉
                .filter(categoryEntity -> {
                    // //只有1行，可以把return和大括号去掉,
                    return categoryEntity.getParentCid() == 0;
                })
                .map(categoryEntity -> {
                    //2、根据大类，找到所有的小类 （递归的方式实现） //需要一个方法,查所有的小类
                    categoryEntity.setChildrens(getCategoryChildrens(categoryEntity, categoryEntityList));
                    return categoryEntity;

                    //排序：热点的排在前面
                })
                .sorted((entity1, entity2) -> {
                    //如果为null，就设置为0，否则，就设置为本身的值，然后再比较，防止空指针异常
                    return (entity1.getSort() == null ? 0 : entity1.getSort()) - (entity2.getSort() == null ? 0 : entity2.getSort());
                }).collect(Collectors.toList());

        return list;
    }

    /**
     * 逻辑批量删除
     *
     * @param ids       类别的id数组
     */
    @Override
    public void removeCategoryByIds(List<Long> ids) {
        //TODO 1.检查类别数据，是否在其他业务中使用，如果使用了，就不能删除了(后续再做)

        //批量删除（mybatis自带的逻辑删除方法）
        baseMapper.deleteBatchIds(ids);
    }

    /**
     * 查找该大类下的所有小类   递归实现
     *  注意：递归的结果在于filter过滤
     * @param categoryEntity 某个大类类目
     * @param entityList     大类目下的所有的类目数据
     * @return entity        某个大类下的所有小类
     */
    private List<CategoryEntity> getCategoryChildrens(
            CategoryEntity categoryEntity,
            List<CategoryEntity> entityList) {

        List<CategoryEntity> collectList = entityList.stream()
                .filter(entity -> {
                    //根据大类，找到它直属的小类
                    //当前查询到的所有数据，如果它的父编号==它当前的编号时，说明是直属的小类集合
                    //（注意：Long类型的数据比较，不在 -128-127之间的数据是new Logn()的对象 ）
                    return entity.getParentCid().equals(categoryEntity.getCatId());
                })
                //小类中，有可能还有小类。递归去找
                .map(entity -> {
                    // 根据这个小类，递归找到对应的小小类
                    entity.setChildrens(getCategoryChildrens(entity, entityList));
                    return entity;
                })
                //排序
                .sorted((entity1, entity2) -> {
                    //如果为null，就设置为0，否则，就设置为本身的值，然后再比较，防止空指针异常
                    return (entity1.getSort() == null ? 0 : entity1.getSort()) - (entity2.getSort() == null ? 0 : entity2.getSort());
                }).collect(Collectors.toList());
        return collectList;
    }

    /**
     * 根据类别编号查询出对应的父组件,找到对应的【一级、二级、三级】类目数据
     *
     * @param catelogId 所属分类id
     * @return
     */
    @Override
    public Long[] findCateLogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        //根据catelogId所属分类id，递归找到父id
        List<Long> parentPath = findParentPath(catelogId, paths);//【 225，34,2 】
        //【 225，34,2 】 进行转置为 【 2、34、225 】
        Collections.reverse(parentPath);
        return parentPath.toArray(new Long[parentPath.size()]);
    }


    /**
     * 根据catelogId所属分类id，递归找到父id
     * ——前端传的是： 【2、34、225 】，这里递归找到的是  【 225，34,2 】
     *
     * @param catelogId
     * @param paths
     * @return
     */
    private List<Long> findParentPath(Long catelogId, List<Long> paths) {
        paths.add(catelogId);
        CategoryEntity entity = this.getById(catelogId);
        if (entity.getParentCid() != 0) {
            findParentPath(entity.getParentCid(), paths);
        }
        return paths;
    }
}
